/*
 * Albareth - an action roleplaying game.
 * Copyright (C) 2008 Jens Anuth
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 *   02110-1301, USA.
 * 
 * $Id: MazeGenerator.java 37 2008-05-06 19:30:07Z jens464 $
*/
package albareth.generator;

import albareth.core.GameMap;
import albareth.core.LevelSpawnConfiguration;
import albareth.util.Location;
import frosch.util.Direction;
import frosch.util.FToolkit;
import frosch.util.Range;




/**
 * Implementation of an algorithm to create
 * random generated mazes.
 */
public class MazeGenerator extends MapGenerator
{
    
    private GameMap _maze = null;
    
    //private char[][] __maze = null;

    private int _width = 0;
    private int _height = 0;

    private int _currentX = 0;
    private int _currentY = 0;
    
    private int _corridorBreadth;
    private double _roomRate;
    
    private int _recursions = 0;

    private boolean[][] _environment = null;


    public MazeGenerator(int corridorBreadth, double roomRate)
    {
        if (roomRate < 0.0 || roomRate > 1.0) {
            throw new IllegalArgumentException("roomRate must be within (0,1): " + roomRate);
        }
        _corridorBreadth = corridorBreadth;
        _roomRate = roomRate;
    }
    
    public void generateMap(int width, int height)
    {
        //generate a simple maze:
        map = generateMaze(width, height);

        //add some rooms to the maze and populate them:
        addRooms(map,
                 (int)(width * height * _roomRate * .02),
                 new Range(3, 6), new Range(3, 6),
                 spawnConfig);
        
    }
    
    GameMap generateMaze(int width, int height)
    {
        GameMap dungeon = new GameMap(width, height);
        MapGeneratorTools.fillMapRect(dungeon, 0, 0, width, height, DEFAULT_WALL);
        
        GameMap tempDungeon = 
            new GameMap(
                    (int)Math.floor(width / _corridorBreadth),
                    (int)Math.floor(height / _corridorBreadth)
            );

        int startX = FToolkit.rand(1, tempDungeon.getWidth() - 2);
        int startY = FToolkit.rand(1, tempDungeon.getHeight() - 2);

        makeMaze(tempDungeon, startX, startY, Direction.NORTH, -1, true);
        
        //expand the map to corridorBreadth:
        for (int x = 0; x < tempDungeon.getWidth(); x++)
        {
            for (int y = 0; y < tempDungeon.getHeight(); y++)
            {
                MapGeneratorTools.fillMapRect(
                        dungeon,
                        x * _corridorBreadth,
                        y * _corridorBreadth,
                        _corridorBreadth,
                        _corridorBreadth,
                        tempDungeon.getGround(x, y)
                );
            }
        }
        
        entranceLocation = new Location(
                startX * _corridorBreadth,
                startY * _corridorBreadth, dungeon
        );
        
        exitLocation = new Location(
                exitLocation.getX() * _corridorBreadth,
                exitLocation.getY() * _corridorBreadth, dungeon
        );

        return dungeon;
    }
    
    /**
     * Add a number of rooms to the maze.
     */
    void addRooms(GameMap map,
                  int amount, 
                  Range widthRange, Range heightRange,
                  LevelSpawnConfiguration spawnConfig)
    {
        for (int i = 0 ; i < amount; i++)
        {
            int w = widthRange.getRandomValue();
            int h = heightRange.getRandomValue();
            int x = FToolkit.rand(1, map.getWidth() - 2 - w);
            int y = FToolkit.rand(1, map.getHeight() - 2 - h);
            
            Room room = new Room(x, y, w, h, map);
            room.drawSelf();
            room.fillWithMonsterSpawn();
        }
    }

    /**
     * Starts maze generating algorithm.
     * @param maze the array which holds the maze.
     * @param startX the x-coordinate to start from, within the maze.
     * @param startY the yx-coordinate to start from, within the maze.
     * @param the initial facing the facing of the 'pointer' that draws the maze.
     * @param recDepth the maximal recursion depth of the algorithm. Algorithm
     * stops and leaves an unfinished maze when reaching this dephts.
     * @param init true when the array containing the maze shall be
     * cleared before running the algorithm on it.
     */
    void makeMaze(GameMap maze, int startX, int startY,
        int startFacing, int recDepth, boolean init)
    {
        //__rnd = new Random();
        _environment = new boolean[3][3];
        //__tmpList = new Vector();
        //__dirList = new Vector();

        _maze = maze;
        //_startX = startX;
        //_startY = startY;
        _currentX = startX;
        _currentY = startY;
        exitLocation = null;
        _width = _maze.getWidth();
        _height = _maze.getHeight();

        if (init)
        {
            MapGeneratorTools.fillMapRect(_maze, 0, 0,
                    _width, _height, DEFAULT_WALL);
            /*
            for (int a = 0; a < _dungeonSizeX; a++)
                for (int b = 0; b < _dungeonSizeY; b++)
                {
                    _maze.setGround(Ground.WALL1, a, b);
                    //__maze[a][b] = Dungeon.WALL;
                }
                */
        }

        //__currentFacing = startFacing;
        _recursions = recDepth;

        carve(Direction.NOWHERE);

        if ((_recursions > -1) && (_recursions == (recDepth - 1)))
        {
            _maze.setGround(DEFAULT_WALL, _currentX, _currentY);
            //__maze[__currentX][__currentY] = Dungeon.WALL;
            return; //ready
        }

        //__maze[startX][startY] = Dungeon.FLOOR;
        _maze.setGround(DEFAULT_FLOOR, startX, startY);

        return; //ready
    }


    private void carve(int cameFrom)
    {
        if (_recursions > 0) _recursions--;
        if (_recursions == 0) {
            if (exitLocation == null)
                exitLocation = new Location(_currentX, _currentY, _maze);
            return;
        }

        //__maze[__currentX][__currentY] = Dungeon.FLOOR;
        _maze.setGround(DEFAULT_FLOOR, _currentX, _currentY);

        //int[] dirs = Toolkit.tk().getRandomIntSequence(5);
        int[] dirs = FToolkit.getRandomIntSequence(4);

        int dirnum = 0;
        while(dirnum < 4)
        {
            //System.err.println(dirs[dirnum] + 1);

            if ((dirs[dirnum] == 0) && (!(cameFrom == Direction.NORTH)) && goNorth())
                    carve(Direction.SOUTH);
            else if ((dirs[dirnum] == 1) && (!(cameFrom == Direction.SOUTH)) && goSouth())
                    carve(Direction.NORTH);
            else if ((dirs[dirnum] == 2)  && (!(cameFrom == Direction.WEST))  && goWest())
                    carve(Direction.EAST);
            else if ((dirs[dirnum] == 3)  && (!(cameFrom == Direction.EAST))  && goEast())
                    carve(Direction.WEST);
            dirnum++;
        }

        if (cameFrom == Direction.NORTH) _currentY--;
        if (cameFrom == Direction.SOUTH) _currentY++;
        if (cameFrom == Direction.EAST)  _currentX++;
        if (cameFrom == Direction.WEST)  _currentX--;

        if (exitLocation == null) {
            exitLocation = new Location(_currentX, _currentY, _maze);
        }
        return;

    }





    private boolean goNorth()
    {
        if (gotPermission(_currentX, _currentY - 1, Direction.SOUTH))
        {
            _currentY--;
            return(true);
        }
        return(false);
    }


    private boolean goSouth()
    {
        if (gotPermission(_currentX, _currentY + 1, Direction.NORTH))
        {
            _currentY++;
            return(true);
        }
        return(false);
    }


    private boolean goEast()
    {
        if (gotPermission(_currentX + 1, _currentY, Direction.WEST))
        {
            _currentX++;
            return(true);
        }
        return(false);
    }


    private boolean goWest()
    {
        if (gotPermission(_currentX - 1, _currentY, Direction.EAST))
        {
            _currentX--;
            return(true);
        }
        return(false);
    }


    private boolean gotPermission(int x, int y, int cameFrom)
    {

        if ((y < 1) || (y >= _height)) return false;
        if ((x < 1) || (x >= _width)) return false;
        //if (__maze[x][y] == Dungeon.FLOOR) return false;
        if (_maze.getGround(x, y) == DEFAULT_FLOOR) return false;

        lookupEnvironment(x, y);

        if (cameFrom == Direction.EAST)
        {
            if (_environment[1][0]) return false;
            if (_environment[1][2]) return false;
            if (_environment[2][0]) return false;
            if (_environment[2][1]) return false;
            if (_environment[2][2]) return false;
        }

        if (cameFrom == Direction.WEST)
        {
            if (_environment[0][0]) return false;
            if (_environment[0][1]) return false;
            if (_environment[0][2]) return false;
            if (_environment[1][0]) return false;
            if (_environment[1][2]) return false;
        }

        if (cameFrom == Direction.NORTH)
        {
            if (_environment[0][0]) return false;
            if (_environment[0][1]) return false;
            if (_environment[1][0]) return false;
            if (_environment[2][0]) return false;
            if (_environment[2][1]) return false;
        }

        if (cameFrom == Direction.SOUTH)
        {
            if (_environment[0][1]) return false;
            if (_environment[0][2]) return false;
            if (_environment[1][2]) return false;
            if (_environment[2][1]) return false;
            if (_environment[2][2]) return false;
        }

        return(true);
    }



    private void lookupEnvironment(int x, int y)
    {

        for (int a = -1; a < 2; a++)
        {
            for (int b = -1; b < 2; b++)
            {
                if (((x - b) >= _width) || ((x - b) < 0))
                    _environment[b + 1][a + 1] = true;
                else if (((y - a) >= _height) || ((y - a) < 0))
                    _environment[b + 1][a + 1] = true;

                else if (_maze.getGround(x - b, y - a) == DEFAULT_FLOOR)
                    _environment[b + 1][a + 1] = true;
                else
                    _environment[b + 1][a + 1] = false;
            }
        }


    }



    /**
     * For testing only.
     */
/*
    public static void testPrint(int maze[][])
    {
        for (int a = 0; a < maze.length; a++)
        {
            for (int b = 0; b < maze[a].length; b++)
            {
                System.err.print(maze[a][b]);
            }
            System.err.print('\n');
        }
    }
*/
    /**
     * For testing only.
     */
/*
    public static void main(String args[])
    {
        MazeBuilder m = new MazeBuilder();
        int maze[][] = new int[20][20];
        m.makeMaze(maze, 1, 1, 2, 1000);

        testPrint(maze);
    }
*/

}
